﻿using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

namespace Orz.Framework.Foundation
{
    /// <summary>
    /// Windows API invoker.
    /// </summary>
    public class WinDllInvoker
    {
        #region DllImport

        [System.Runtime.InteropServices.DllImport("kernel32")]
        private static extern IntPtr LoadLibrary(string lpLibFileName);

        [System.Runtime.InteropServices.DllImport("kernel32")]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

        [System.Runtime.InteropServices.DllImport("kernel32")]
        private static extern IntPtr FreeLibrary(IntPtr hLibModule);

        #endregion

        /// <summary>
        /// Invoke Windows api
        /// </summary>
        /// <example>
        /// object obj = WinDllInvoker.Invoke("Kernel32.dll", "Beep", new object[] { 750, 300 }, typeof(void));
        /// </example>
        /// <param name="fileName">Dll file.</param>
        /// <param name="funName">Method name.</param>
        /// <param name="objParams">The params of method.</param>
        /// <param name="returnType">The return type.</param>
        /// <returns>The api returned value.</returns>
        public static object Invoke(string fileName, string funName, object[] objParams, Type returnType)
        {
            IntPtr libHandle = IntPtr.Zero;

            try
            {
                // get the method address
                libHandle = LoadLibrary(fileName);
                if (libHandle == IntPtr.Zero) return null;
                IntPtr procAddres = GetProcAddress(libHandle, funName);
                if (procAddres == IntPtr.Zero) return null;

                // get the parameter type
                Type[] paramTypes = new Type[objParams.Length];
                for (int i = 0; i < objParams.Length; ++i)
                {
                    paramTypes[i] = objParams[i].GetType();
                }

                // construct the invoke method
                AssemblyName asembyName = new AssemblyName();
                asembyName.Name = "WinDllInvoke_Assembly";
                AssemblyBuilder asembyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asembyName, AssemblyBuilderAccess.Run);
                ModuleBuilder moduleBuilder = asembyBuilder.DefineDynamicModule("WinDllInvoke");
                MethodBuilder methodBuilder = moduleBuilder.DefineGlobalMethod("InvokeFun", MethodAttributes.Public | MethodAttributes.Static, returnType, paramTypes);

                // get a ILGenerator
                ILGenerator IL = methodBuilder.GetILGenerator();
                for (int j = 0; j < paramTypes.Length; ++j)
                {
                    // put the parameter in stack
                    if (paramTypes[j].IsValueType)
                    {
                        IL.Emit(OpCodes.Ldarg, j); //By Value
                    }
                    else
                    {
                        IL.Emit(OpCodes.Ldarga, j); //By Addrsss
                    }
                }

                // processor type
                if (IntPtr.Size == 4)
                {
                    IL.Emit(OpCodes.Ldc_I4, procAddres.ToInt32());
                }
                else if (IntPtr.Size == 8)
                {
                    IL.Emit(OpCodes.Ldc_I8, procAddres.ToInt64());
                }
                else
                {
                    throw new PlatformNotSupportedException("Unknow processor type.");
                }

                IL.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, returnType, paramTypes);
                // return
                IL.Emit(OpCodes.Ret);
                moduleBuilder.CreateGlobalFunctions();

                // get the methodinfo
                MethodInfo methodInfo = moduleBuilder.GetMethod("InvokeFun");

                return methodInfo.Invoke(null, objParams);
            }
            catch { return null; }
            finally
            {
                if (libHandle != IntPtr.Zero) FreeLibrary(libHandle);
            }
        }
    }
}
